<?php
/**
 * @author 595949289@qq.com
 * @date 2025/3/18 10:49
 */

namespace CuiFox\gii\generators\dictionary;

use Yii;
use yii\db\Connection;
use yii\gii\CodeFile;
use yii\helpers\Inflector;

class Generator extends \yii\gii\Generator
{
    public $db = 'db';
    public $ns = 'app\dictionaries';

    /**
     * @var string
     */
    public $tableName = '';

    public $baseClass = '\CuiFox\yii\base\Dictionary';

    protected $classNames = [];

    public function init()
    {
        parent::init();
        $this->tableName = $this->getTablePrefix() . 'dictionary';
    }

    public function getName()
    {
        return 'Dictionary Generator';
    }

    public function getDescription()
    {
        return 'This generator generates Dict classes for dictionary tables.';
    }

    public function generate()
    {
        $files = [];
        $db = $this->getDbConnection();
        $query = $db->createCommand("select * from {$this->tableName} where parent is null");
        foreach ($query->queryAll() as $row) {
            $className = $this->generateClassName($row['value']);
            $const = $db->createCommand("select * from {$this->tableName} where parent = :id", [
                ':id' => $row['id'],
            ])->queryAll();
            foreach ($const as &$it) {
                $it['key'] = is_numeric($it['value']) ? 'K' . $it['value'] : preg_replace('/[^A-Za-z0-9]/', '_',
                    strtoupper($it['value']));
                $it['value'] = is_numeric($it['value']) ? $it['value'] : "'{$it['value']}'";
            }
            $params = [
                'className' => $className,
                'const' => $const,
                'name' => strtoupper($row['value']),
            ];
            $files[] = new CodeFile(
                Yii::getAlias('@' . str_replace('\\', '/', $this->ns)) . '/' . $className . '.php',
                $this->render('dictionary.php', $params)
            );
        }
        return $files;
    }

    protected function generateClassName($name)
    {
        if (!empty($this->classNames[$name])) {
            return $this->classNames[$name];
        }

        $fullName = $name;
        $this->classNames[$fullName] = Inflector::camelize(Inflector::camel2words($name));

        return $this->classNames[$fullName];
    }

    /**
     * Returns the `tablePrefix` property of the DB connection as specified
     *
     * @return string
     * @since 2.0.5
     * @see getDbConnection
     */
    public function getTablePrefix()
    {
        $db = $this->getDbConnection();

        return $db === null ? '' : $db->tablePrefix;
    }

    /**
     * Returns the database connection as specified by [[db]].
     *
     * @return Connection|null database connection instance
     */
    protected function getDbConnection()
    {
        return Yii::$app->get($this->db, false);
    }

    public function attributeLabels()
    {
        return array_merge(parent::attributeLabels(), [
            'ns' => 'Namespace',
            'db' => 'Database Connection ID',
            'tableName' => 'Table Name',
            'baseClass' => 'Base Class Name',
        ]);
    }

    /**
     * @inheritdoc
     */
    public function hints()
    {
        return array_merge(parent::hints(), [
            'ns' => 'This is the namespace of the ActiveRecord class to be generated, e.g., <code>app\models</code>',
            'db' => 'This is the ID of the DB application component.',
            'tableName' => 'This is the name of the DB table that the new ActiveRecord class is associated with, e.g. <code>post</code>.
                The table name may consist of the DB schema part if needed, e.g. <code>public.post</code>.
                The table name may end with asterisk to match multiple table names, e.g. <code>tbl_*</code>
                will match tables who name starts with <code>tbl_</code>. In this case, multiple ActiveRecord classes
                will be generated, one for each matching table name; and the class names will be generated from
                the matching characters. For example, table <code>tbl_post</code> will generate <code>Post</code>
                class.',
            'generateRelationsFromCurrentSchema' => 'This indicates whether the generator should generate relations from current schema or from all available schemas.',
            'useClassConstant' => 'Use the `::class` constant instead of the `::className()` method.',
            'generateLabelsFromComments' => 'This indicates whether the generator should generate attribute labels
                by using the comments of the corresponding DB columns.',
        ]);
    }

    public function rules()
    {
        return array_merge(parent::rules(), [
            [['db', 'tableName', 'baseClass'], 'trim'],
            [
                ['ns'],
                'filter',
                'filter' => static function ($value) {
                    return $value === null ? null : trim($value, ' \\');
                },
            ],
            [['db', 'ns', 'tableName', 'baseClass'], 'required'],
            [['db'], 'match', 'pattern' => '/^\w+$/', 'message' => 'Only word characters are allowed.'],
            [
                ['ns', 'baseClass'],
                'match',
                'pattern' => '/^[\w\\\\]+$/',
                'message' => 'Only word characters and backslashes are allowed.',
            ],
            [
                ['tableName'],
                'match',
                'pattern' => '/^([\w ]+\.)?([\w\* ]+)$/',
                'message' => 'Only word characters, and optionally spaces, an asterisk and/or a dot are allowed.',
            ],
            [['db'], 'validateDb'],
            [['ns'], 'validateNamespace'],
            [['tableName'], 'validateTableName'],
            [['enableI18N'], 'boolean'],
            [['messageCategory'], 'validateMessageCategory', 'skipOnEmpty' => false],
        ]);
    }

    public function validateDb()
    {
        if (!Yii::$app->has($this->db)) {
            $this->addError('db', 'There is no application component named "db".');
        } elseif (!Yii::$app->get($this->db) instanceof Connection) {
            $this->addError('db', 'The "db" application component must be a DB connection instance.');
        }
    }

    /**
     * Validates the namespace.
     *
     * @param string $attribute Namespace variable.
     */
    public function validateNamespace($attribute)
    {
        $value = $this->$attribute;
        $value = ltrim($value, '\\');
        $path = Yii::getAlias('@' . str_replace('\\', '/', $value), false);
        if ($path === false) {
            $this->addError($attribute, 'Namespace must be associated with an existing directory.');
        }
    }

    public function validateTableName($attribute)
    {
        $db = $this->getDbConnection();
        if ($db === null) {
            return [];
        }
        if (($table = $db->getTableSchema($this->tableName, true)) === null) {
            $this->addError($attribute, 'Table must be existing.');
        }
    }
}